Synchronized锁升级(偏向锁、轻量锁、重量锁)

您所在的位置:网站首页 synchronized偏向锁 撤销 Synchronized锁升级(偏向锁、轻量锁、重量锁)

Synchronized锁升级(偏向锁、轻量锁、重量锁)

2023-03-24 11:25| 来源: 网络整理| 查看: 265

高并发时,同步调用应该考虑到锁的性能消耗。能用无锁的数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能使用对象锁,就不用类锁。

锁的升级:

无锁 => 偏向锁 => 轻量级锁 => 重量级锁

偏向锁:当一段同步代码一直被同一个线程多次访问,那么该线程在后续的访问时会自动获取锁 。

偏向锁:

当同步代码首次被一个线程访问,那么就会在Mark Word记录该线程的ID,从无锁状态(001)变成偏向锁(101),当同步代码执行结束,该线程并不会释放锁。

当下一次同步代码被访问时,那么就会检测该线程ID与锁的Mark Word 中的线程ID是否是相同。

相同:则直接进入同步代码,因为之前没有释放锁

不同:表示发生了竞争,会尝试使用CAS来替换Mark Word里面的线程ID。竞争成功则会替换Mark Word 里面的线程ID,竞争失败可能会变成轻量级锁。

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放锁的。

偏向锁默认在程序启动4秒后才会开启,可以用下面参数更改时间

开启偏向锁(jdk6之后默认开启):-XX:+UseBiasedLocking

将偏向锁延迟时间由4000ms改为0:-XX:BiasedLockingStartupDelay=0

偏向锁的撤销:

撤销需要等待全局安全点,同时检查持有偏向锁的线程是否还在执行。

第一个线程正在执行synchronized方法(处于同步代码块),他还没有执行完,其他线程来争抢,该偏向锁会被取消并出现锁升级。此时轻量级锁由原持有偏向锁线程持有,继续执行其同步代码,而在竞争的会进入自旋等待获得该轻量锁。

第一个线程执行完synchronized方法(退出同部分代码块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向。

由于维护成本高,偏向锁在jdk15及之后被移除了。

轻量锁

轻量级锁是为了在线程近乎交替执行同步块时提高性能

在没有多线程的前提下,通过CAS减少重量级锁使用操作系统互斥产生的性能损耗。

轻量锁的加锁:JVM会为每个线程创建用于存储锁的记录空间,官方成为 Displaced Mark Word。若线程获得锁时发现是轻量锁,则会把锁的 Mark Word 复制到Displaced Mark Word 里面。

轻量锁的释放:当前线程会使用CAS操作将Displaced Mark Word里面的内容复制回Mark Word里面。

自旋抢占轻量锁,当自旋到达一定次数依然没有成功获取锁将升级为重量锁。

jdk6之前:

默认10次自旋未成功升级锁 使用VM参数修改 -XX:PreBlockSpin=10

或者自旋线程超过了cpu核心数一半

jdk6之后:自适应自旋锁。JVM底层优化,若线程上次自旋成功获取锁,那么下一次就会增加自旋的次数,以保证更大的可能性获取锁;若线程很少自旋成功获取锁,那么下次就会减少自旋的次数,避免cpu空转。

轻量锁与偏向锁的区别:

争夺轻量锁失败,自旋锁会尝试枪锁。

轻量锁每次执行完同步代码块都会释放锁,而偏向锁只有在发生竞争时才会释放锁。

重量锁

ObjectMonitor类 操作系统管程monitor

锁升级后hashcode去哪儿了?

偏向锁:Mark Word 存储的是偏向的线程ID

轻量锁:Mark Word 存储的是指向线程栈中Lock Record 的指针

重量锁:Mark Word 存储的是指向堆中的monitor对象的指针

无锁状态:Mark Word 可以存储 hash code值。当对象的hashCode()方法第一次被调用时,JVM会生成对应的hash code值并存储到Mark Word中。

对于偏向锁:在线程获取偏向锁时,会用Thread ID和epoch值覆盖掉hash code所在的位置。如果一个对象的hashCode() 方法已经被调用过一次之后,这个对象不能被设置偏向锁。因为可以的话,那么Mark Word中的hash code必然会被偏向线程id给覆盖。

对于轻量级锁:JVM会在当前线程的栈帧中创建Lock Record(锁记录空间),用于存储锁对象的Mark Word拷贝。所以轻量级锁与hash code 共存。释放锁后会将信息回写到对象头。

对于重量级锁:代表重量级锁的ObjectMonitor类里有字段记录非枷锁状态下Mark Word,锁释放后也会将信息写回到对象头。

在获取偏向锁之前调用 hashCode() ,会升级成轻量级锁。(也就是 空白Mark Word 先存储hashcode 后在存储线程ID)

在获取偏向锁之后调用 hashCode(),会升级成重量级锁。(也就是 空白Mark Word先存储线程ID后又想存储hashCode)

小总结

先自旋,不行再阻塞。

如果同步代码块执行时间过长,那么轻量级锁自旋带来的性能消耗就比使用重量级锁更加严重。

JIT编译器对锁的消除 锁消除

下面代码发生了锁消除,因为每个线程都自己new了一个对象作为自己的锁,这样是没有意义的(应是多个线程抢同一把锁),jit编译器会自己忽略它(逃逸分析)。

public class SynchronizedDemo1 { static Object staticObject = new Object(); public void m1(){ Object o = new Object(); synchronized (o){ System.out.println(Thread.currentThread().getName()+"\t"+o.hashCode()+"\t"+staticObject.hashCode()); } } public static void main(String[] args) { SynchronizedDemo1 synchronizedDemo1 = new SynchronizedDemo1(); for (int i = 0; i < 10; i++) { new Thread(()->{ synchronizedDemo1.m1(); },String.valueOf(i)).start(); } } }

锁粗化

方法中前后相邻都是同一个锁对象,JIT会把几个synchronized合并成一个大块。

一次申请和释放锁,提高了性能。

new Thread(() -> { synchronized (o) { System.out.println(111); } synchronized (o) { System.out.println(222); } synchronized (o) { System.out.println(333); } synchronized (o) { System.out.println(444); } }).start(); new Thread(() -> { synchronized (o) { System.out.println(111); System.out.println(222); System.out.println(333); System.out.println(444); } }).start();


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3